use core::{ptr::copy_nonoverlapping, slice};

use driver_utils::conn::xor_sum;
use utils::{ByteBuffer, MutCell};

use crate::app::{
    config::address::IPC,
    driver::cmd_can::{self, Frame},
    proto::{END, HEAD0, HEAD1, MIN_LEN},
};

#[cfg(feature = "cp_drop")]
use crate::app::config::address::{CARGO1, CARGO2, DOOR, HEAT, WEIGHT};

struct RecvBuffer<const SIZE: usize, const SRC: u8> {
    buf: ByteBuffer<SIZE>,
    recv_index: MutCell<usize>,
    dst: MutCell<u8>,
}

impl<const SIZE: usize, const SRC: u8> RecvBuffer<SIZE, SRC> {
    const fn const_new() -> Self {
        Self {
            buf: ByteBuffer::const_new(),
            recv_index: MutCell::const_new(0),
            dst: MutCell::const_new(0),
        }
    }

    fn put(&self, frame: Frame) {

        let seq = frame.pkg_seq();
        unsafe {
            let index = if seq == 0 {
                *self.dst.as_mut() = frame.dst();
                *self.recv_index.as_mut() = 0;
                0
            } else {
                *self.recv_index
            };

            if frame.dst() != *self.dst {
                return;
            }

            let data = frame.data();
            if (index + data.len()) > SIZE {
                return;
            }

            copy_nonoverlapping(data.as_ptr(), self.buf.as_mut_ptr().add(index), data.len());
            (*self.recv_index.as_mut()) += data.len();

            if frame.is_complete() {
                let buf = slice::from_raw_parts(self.buf.as_ptr(), *self.recv_index);
                Self::on_complete(buf, frame.dst());
            }
        }
    }

    fn on_complete(buf: &[u8], _dst: u8) {
        let len = buf.len();
        if len < (MIN_LEN as usize) {
            return;
        }
        if buf[0] != HEAD0 {
            return;
        }
        if buf[1] != HEAD1 {
            return;
        }
        if buf[2] != (len as u8) {
            return;
        }
        let sum = xor_sum(&buf[3..(len - 2)]);
        if buf[len - 2] != sum {
            return;
        }

        if buf[len - 1] != END {
            return;
        }

        #[cfg(feature = "cp_drop")]
        if _dst == IPC {
            use super::super::driver::cmd_uart;
            cmd_uart::write(buf);
            return;
        }

        #[cfg(not(feature = "cp_drop"))]
        if SRC == IPC {
            use super::super::proto::RequestRef;
            use super::dispatcher::DISPATCHER;
            let request = RequestRef::new(buf);
            DISPATCHER.exec(request);
        }
    }
}

const RECV_BUF_SIZE: usize = 255;

#[cfg(feature = "cp_drop")]
static SRC_CARGO1_RECV_BUF: RecvBuffer<RECV_BUF_SIZE, CARGO1> = RecvBuffer::const_new();

#[cfg(feature = "cp_drop")]
static SRC_CARGO2_RECV_BUF: RecvBuffer<RECV_BUF_SIZE, CARGO2> = RecvBuffer::const_new();

#[cfg(feature = "cp_drop")]
static SRC_WEIGHT_RECV_BUF: RecvBuffer<RECV_BUF_SIZE, WEIGHT> = RecvBuffer::const_new();

#[cfg(feature = "cp_drop")]
static SRC_HEAT_RECV_BUF: RecvBuffer<RECV_BUF_SIZE, HEAT> = RecvBuffer::const_new();

#[cfg(feature = "cp_drop")]
static SRC_DOOR_RECV_BUF: RecvBuffer<RECV_BUF_SIZE, DOOR> = RecvBuffer::const_new();

#[cfg(not(feature = "cp_drop"))]
static SRC_IPC_RECV_BUF: RecvBuffer<RECV_BUF_SIZE, IPC> = RecvBuffer::const_new();

pub fn can_recv_loop() {
    loop {
        let frame = cmd_can::take();

        match frame.src() {
            #[cfg(not(feature = "cp_drop"))]
            IPC => SRC_IPC_RECV_BUF.put(frame),

            #[cfg(feature = "cp_drop")]
            CARGO1 => SRC_CARGO1_RECV_BUF.put(frame),

            #[cfg(feature = "cp_drop")]
            CARGO2 => SRC_CARGO2_RECV_BUF.put(frame),

            #[cfg(feature = "cp_drop")]
            WEIGHT => SRC_WEIGHT_RECV_BUF.put(frame),

            #[cfg(feature = "cp_drop")]
            HEAT => SRC_HEAT_RECV_BUF.put(frame),

            #[cfg(feature = "cp_drop")]
            DOOR => SRC_DOOR_RECV_BUF.put(frame),
            _ => {}
        };
    }
}
